"""
This script contains all callback functions that might be called during the procedure

Created: May 1st?
Version 1.0: May 7th

Author: Derek Zhang
Todo:
- Optimality condition improvement
- Code optimization and organization
"""

import cplex.callbacks as CPX_CB

class MySolve(CPX_CB.SolveCallback):
    def __call__(self):
        if self.get_num_nodes() < 1:
            self.solve(self.method.primal)
        else:
            self.solve(self.method.dual)
        status = self.get_cplex_status()
        self.use_solution()


class CheckBigM(CPX_CB.CutCallback):

    def __call__(self):
        self.times_called += 1
        local_flag = 0
        #rhs = self.rhs
        #xi = self.xi
        self.node_y_val = self.get_values(self.y_name)
        #node_v_val = self.get_values(self.v_name)
        self.node_x_val = self.get_values(self.x_name)
        self.node_v_val = self.get_values(self.v_name)
        self.slack = self.get_linear_slacks(self.bigm_rowname)
        
        bigm = self.bigm_coef

        #print self.get_num_nodes(),
        #print self.get_num_remaining_nodes()
        
        feas_ind = (sum(self.get_feasibilities(self.y_name)) == 0)
        #if not feas_ind:
            #self.flag[0] = 1
        if 0:
            print "factional solution found, go to the next node"
            print "Current incumbent", self.get_incumbent_objective_value()
            
        else:
            #reset the bigm indicator vector
            for i in range(len(self.bigm_ind)):
                self.bigm_ind[i] = 0
            for i in self.I:
                for s in self.S:
                    ind = i + s*self.NI

                    #if abs(node_y_val[s] - 1.0) <= 0.00001 and abs(slack[ind]) <= 0.00001:
                    # 

                    """
                    The main Checking routine is here:
                    1. y is positive
                    2. slackness is positive
                    3. bigm value if within the bound, which is strengthed value!!!
                    """

                    # compare with the org rhs
                    #if abs(self.node_y_val[s]) > 0.000001 and abs(self.slack[ind]) <= 0.000001 and self.bigm_coef[ind] < self.rhs[s][i]:
                    # compare with the strengthened rhs
                    #if abs(self.node_y_val[s] - 1.0) < 0.0001 and abs(self.slack[ind]) <= 0.0001 and self.bigm_coef[ind] < self.rhs[s][i] - self.xi[i]:

                    if abs(self.node_y_val[s]) > 0.0000001 and abs(self.slack[ind]) <= 0.0000001 and self.bigm_coef[ind] < self.rhs[s][i]:

                        local_flag = 1
                        self.bigm_ind[ind] = 1
                        
                    """
                    if abs(self.node_y_val[s]) > 0.000001 and abs(self.slack[ind]) <= 0.000001:
                        if self.rhs[s][i] - self.xi[i] >= 0 and self.bigm_coef[ind] < self.rhs[s][i] - self.xi[i]:
                            local_flag = 1
                            self.bigm_ind[ind] = 1


                        elif self.rhs[s][i] - self.xi[i] < 0 and self.bigm_coef[ind] < self.rhs[s][i]:
                            local_flag = 1
                            self.bigm_ind[ind] = 1
                    """
                            
            if self.Print and local_flag == 1:
                self.print_nodeinfo()

            if local_flag == 1:
                print "resolve the problem"
                #self.incumbent[0] =self.get_cutoff()
                #self.incumbent[0] =self.get_objective_value()
                self.incumbent[0] = self.get_incumbent_objective_value()
                print "incumbent is ", self.incumbent[0]
                self.flag[0] = 0
                self.abort()
                return
            else:
                "all nodes fine, go to the next node"

        #if feas_ind:
    def print_nodeinfo(self):
        print "solution at this node", self.get_objective_value()
        print "incumbent at this node", self.get_incumbent_objective_value()
        print "%4s %4s " %('sce', 'i'),
        print "%10s %10s %10s %10s %10s %10s" % ('x_val', 'v_val', 'y_val', 'slack', 'bigm', 'rhs')
        print '-'*60

        for i in self.I:
            #print 
            for s in self.S:
                ind = i + s*self.NI
                #if 1:
                if self.bigm_ind[ind] == 1:

                    print "%4d %4d  " % (s, i),
                    print "%10f" % (self.node_x_val[i]) ,
                    print "%10f" % (self.node_v_val[i]),
                    print "%10f" % (self.node_y_val[s]),
                    print "%10f" % (self.slack[ind]),
                    print "%10f" % (self.bigm_coef[ind]),
                    print "%10f" % (self.rhs[s][i]),

                    if self.bigm_ind[ind] == 1:
                        print " ***",

                    print ""
                
class CheckSI(CPX_CB.CutCallback):
    def __call__(self):
        self.times_called += 1
        print "\n\n\n"
        #if the node has integer solution go ahead
        
        node_y_val = self.get_values(self.y_name)
        node_v_val = self.get_values(self.v_name)
        node_x_val = self.get_values(self.x_name)

        print "x_val", node_x_val
        print "y_val", node_y_val

        for i in self.I:
            print i, "th variable\n---------------"

            si_coef = []
            si_lhs = node_v_val[i]

            # Choose the largest coefficient
            si_tmp_ceof = [self.rhs[s][i] for s in self.S]
            si_coef_max = max(si_tmp_ceof)

            
            for s in self.S:
                if self.rhs[s][i] >= self.xi[i]:# and node_y_val[s] > 0
                    si_coef += [[self.rhs[s][i], node_y_val[s]]]
                # Always include the coefficient pair with largest coefficient
#                elif abs(self.rhs[s][i] - si_coef_max) < 0.000001:
#                    si_coef += [[self.rhs[s][i], node_y_val[s]]]
            si_coef.sort(reverse=True)
            
            l = len(si_coef)
            print "si_coef lenght", l
            si_coef += [[self.xi[i], 0]]
            #print si_coef
            for k in range(l):
                si_lhs += (si_coef[k][0]-si_coef[k+1][0]) * si_coef[k][1]

            if si_lhs >= si_coef[0][0]:
                print "Star inequality satisfied, abort solving"
            else:
                print "Star inequality violated, valid LP lower bound"
            print "lhs", si_lhs, "rhs", si_coef[0][0]

            """
                if node_y_val[s] > 0 and self.rhs[s][i] >= self.xi[i]:
                    si_ind = (node_v_val[i] + (self.rhs[s][i] - self.xi[i]) * node_y_val[s] >= self.rhs[s][i])
                    #node_y_val[i] + (rhs[]- rhs[]) * node_v_val[i] >= rhs[]:#satisify the SI::
                    if si_ind:
                    #    print node_y_val
                        print "y is positive", i, s
                        print "Star inequality satisfied, abort solving"
                        print "lhs=", node_v_val[i] + (self.rhs[s][i] - self.xi[i]) * node_y_val[s],
                        print "rhs=", self.rhs[s][i]

                        #self.abort()
                        #return
                    #                self.abort()
                    #                return
                    else:
                        print "Star inequality is violated, valid LP lower bound"

                    

                else:
                    print "y is zero, keep going"
            """

class CheckBounds(CPX_CB.CutCallback):
    def __call__(self):
        self.times_called += 1

        # create local variables
        node_y_val = self.get_values(self.y_name)
        node_v_val = self.get_values(self.v_name)
        node_x_val = self.get_values(self.x_name)
        nslack = 0

        for i in self.I:
            #--Case 1: v_val drops below the hard bound then
            # recover the lower bound and treat it as slack

            if node_v_val[i] <= self.v_hlb[i]+0.0001:
                self.v_lb[i] = self.v_hlb[i]
                nslack += 1
                
            #--Case 2: v_val is above the hard lower bound and reaches lower bound
            # we lower the lower bound
            elif abs(node_v_val[i] - self.v_lb[i]) < 0.00001:
                self.v_lb[i] = self.v_lb[i] * 0.95 #change the v_lb here for next iteration

            #--Case 3: v_val is above the soft lower bound
            # we just recognize it to be slack
            else:
                nslack += 1

        if nslack == self.NI: #all slack go to the next node
            self.flag[0] = 1
            """
            print "this node has no binding constraints, continue with traversal"
            print self.get_num_nodes(),
            print self.get_num_remaining_nodes()
            """
        else:
            self.flag[0] = 0
            print "this node has active constraint abort",
            print self.get_num_nodes(),
            print self.get_num_remaining_nodes()
            self.abort()
            return
